package com.ruoyi.common.utils.ip

import com.ruoyi.common.utils.MyStringUtils
import com.ruoyi.common.utils.ServletUtils.request
import org.apache.commons.lang3.StringUtils
import java.net.InetAddress
import java.net.UnknownHostException
import javax.servlet.http.HttpServletRequest

/**
 * 获取IP方法
 *
 * @author ruoyi
 */
object IpUtils {
    private const val REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)"

    // 匹配 ip
    private const val REGX_IP = "(($REGX_0_255\\.){3}$REGX_0_255)"
    private const val REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|($REGX_0_255(\\.\\*){3})|($REGX_0_255\\.$REGX_0_255)(\\.\\*){2}|(($REGX_0_255\\.){3}\\*))"

    // 匹配网段
    private const val REGX_IP_SEG = "($REGX_IP\\-$REGX_IP)"
    @JvmStatic
    val ipAddr: String
        /**
         * 获取客户端IP
         *
         * @return IP地址
         */
        get() = getIpAddr(request)

    /**
     * 获取客户端IP
     *
     * @param request 请求对象
     * @return IP地址
     */
    fun getIpAddr(request: HttpServletRequest?): String {
        if (request == null) {
            return "unknown"
        }
        var ip = request.getHeader("x-forwarded-for")
        if (ip == null || ip.isEmpty() || "unknown".equals(ip, ignoreCase = true)) {
            ip = request.getHeader("Proxy-Client-IP")
        }
        if (ip == null || ip.isEmpty() || "unknown".equals(ip, ignoreCase = true)) {
            ip = request.getHeader("X-Forwarded-For")
        }
        if (ip == null || ip.isEmpty() || "unknown".equals(ip, ignoreCase = true)) {
            ip = request.getHeader("WL-Proxy-Client-IP")
        }
        if (ip == null || ip.isEmpty() || "unknown".equals(ip, ignoreCase = true)) {
            ip = request.getHeader("X-Real-IP")
        }
        if (ip == null || ip.isEmpty() || "unknown".equals(ip, ignoreCase = true)) {
            ip = request.remoteAddr
        }
        return if ("0:0:0:0:0:0:0:1" == ip) "127.0.0.1" else getMultistageReverseProxyIp(ip)
    }

    /**
     * 检查是否为内部IP地址
     *
     * @param ip IP地址
     * @return 结果
     */
    fun internalIp(ip: String): Boolean {
        val addr = textToNumericFormatV4(ip)
        return internalIp(addr) || "127.0.0.1" == ip
    }

    /**
     * 检查是否为内部IP地址
     *
     * @param addr byte地址
     * @return 结果
     */
    private fun internalIp(addr: ByteArray?): Boolean {
        if (MyStringUtils.isNull(addr) || addr!!.size < 2) {
            return true
        }
        val b0 = addr[0]
        val b1 = addr[1]
        // 10.x.x.x/8
        val SECTION_1: Byte = 0x0A
        // 172.16.x.x/12
        val SECTION_2 = 0xAC.toByte()
        val SECTION_3 = 0x10.toByte()
        val SECTION_4 = 0x1F.toByte()
        // 192.168.x.x/16
        val SECTION_5 = 0xC0.toByte()
        val SECTION_6 = 0xA8.toByte()
        return when (b0) {
            SECTION_1 -> true
            SECTION_2 -> {
                if (b1 in SECTION_3..SECTION_4) {
                    return true
                }
                when (b1) {
                    SECTION_6 -> return true
                }
                false
            }

            SECTION_5 -> {
                when (b1) {
                    SECTION_6 -> return true
                }
                false
            }

            else -> false
        }
    }

    /**
     * 将IPv4地址转换成字节
     *
     * @param text IPv4地址
     * @return byte 字节
     */
    fun textToNumericFormatV4(text: String): ByteArray? {
        if (text.isEmpty()) {
            return null
        }
        val bytes = ByteArray(4)
        val elements = text.split("\\.".toRegex()).toTypedArray()
        try {
            var l: Long
            var i: Int
            when (elements.size) {
                1 -> {
                    l = elements[0].toLong()
                    if (l < 0L || l > 4294967295L) {
                        return null
                    }
                    bytes[0] = (l shr 24 and 0xFFL).toInt().toByte()
                    bytes[1] = (l and 0xFFFFFFL shr 16 and 0xFFL).toInt().toByte()
                    bytes[2] = (l and 0xFFFFL shr 8 and 0xFFL).toInt().toByte()
                    bytes[3] = (l and 0xFFL).toInt().toByte()
                }

                2 -> {
                    l = elements[0].toInt().toLong()
                    if (l < 0L || l > 255L) {
                        return null
                    }
                    bytes[0] = (l and 0xFFL).toInt().toByte()
                    l = elements[1].toInt().toLong()
                    if (l < 0L || l > 16777215L) {
                        return null
                    }
                    bytes[1] = (l shr 16 and 0xFFL).toInt().toByte()
                    bytes[2] = (l and 0xFFFFL shr 8 and 0xFFL).toInt().toByte()
                    bytes[3] = (l and 0xFFL).toInt().toByte()
                }

                3 -> {
                    i = 0
                    while (i < 2) {
                        l = elements[i].toInt().toLong()
                        if (l < 0L || l > 255L) {
                            return null
                        }
                        bytes[i] = (l and 0xFFL).toInt().toByte()
                        ++i
                    }
                    l = elements[2].toInt().toLong()
                    if (l < 0L || l > 65535L) {
                        return null
                    }
                    bytes[2] = (l shr 8 and 0xFFL).toInt().toByte()
                    bytes[3] = (l and 0xFFL).toInt().toByte()
                }

                4 -> {
                    i = 0
                    while (i < 4) {
                        l = elements[i].toInt().toLong()
                        if (l < 0L || l > 255L) {
                            return null
                        }
                        bytes[i] = (l and 0xFFL).toInt().toByte()
                        ++i
                    }
                }

                else -> return null
            }
        } catch (e: NumberFormatException) {
            return null
        }
        return bytes
    }

    @JvmStatic
    val hostIp: String
        /**
         * 获取IP地址
         *
         * @return 本地IP地址
         */
        get() {
            try {
                return InetAddress.getLocalHost().hostAddress
            } catch (_: UnknownHostException) {
            }
            return "127.0.0.1"
        }
    @JvmStatic
    val hostName: String
        /**
         * 获取主机名
         *
         * @return 本地主机名
         */
        get() {
            try {
                return InetAddress.getLocalHost().hostName
            } catch (_: UnknownHostException) {
            }
            return "未知"
        }

    /**
     * 从多级反向代理中获得第一个非unknown IP地址
     *
     * @param ip 获得的IP地址
     * @return 第一个非unknown IP地址
     */
    fun getMultistageReverseProxyIp(ip: String?): String {
        // 多级反向代理检测
        var newIp = ip
        if (newIp != null && newIp.indexOf(",") > 0) {
            val ips = newIp.trim { it <= ' ' }.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
            for (subIp in ips) {
                if (!isUnknown(subIp)) {
                    newIp = subIp
                    break
                }
            }
        }
        return MyStringUtils.mySubstring(newIp, 0, 255)
    }

    /**
     * 检测给定字符串是否为未知，多用于检测HTTP请求相关
     *
     * @param checkString 被检测的字符串
     * @return 是否未知
     */
    fun isUnknown(checkString: String?): Boolean {
        return StringUtils.isBlank(checkString) || "unknown".equals(checkString, ignoreCase = true)
    }

    /**
     * 是否为IP
     */
    fun isIP(ip: String): Boolean {
        return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP.toRegex())
    }

    /**
     * 是否为IP，或 *为间隔的通配符地址
     */
    fun isIpWildCard(ip: String): Boolean {
        return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD.toRegex())
    }

    /**
     * 检测参数是否在ip通配符里
     */
    fun ipIsInWildCardNoCheck(ipWildCard: String, ip: String): Boolean {
        val s1 = ipWildCard.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        val s2 = ip.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        var isMatchedSeg = true
        var i = 0
        while (i < s1.size && s1[i] != "*") {
            if (s1[i] != s2[i]) {
                isMatchedSeg = false
                break
            }
            i++
        }
        return isMatchedSeg
    }

    /**
     * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串
     */
    fun isIPSegment(ipSeg: String): Boolean {
        return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG.toRegex())
    }

    /**
     * 判断ip是否在指定网段中
     */
    fun ipIsInNetNoCheck(iparea: String, ip: String): Boolean {
        val idx = iparea.indexOf('-')
        val sips = iparea.substring(0, idx).split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        val sipe = iparea.substring(idx + 1).split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        val sipt = ip.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        var ips = 0L
        var ipe = 0L
        var ipt = 0L
        for (i in 0..3) {
            ips = ips shl 8 or sips[i].toInt().toLong()
            ipe = ipe shl 8 or sipe[i].toInt().toLong()
            ipt = ipt shl 8 or sipt[i].toInt().toLong()
        }
        if (ips > ipe) {
            val t = ips
            ips = ipe
            ipe = t
        }
        return ipt in ips..ipe
    }

    /**
     * 校验ip是否符合过滤串规则
     *
     * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99`
     * @param ip 校验IP地址
     * @return boolean 结果
     */
    @JvmStatic
    fun isMatchedIp(filter: String, ip: String): Boolean {
        if (MyStringUtils.isEmpty(filter) || MyStringUtils.isEmpty(ip)) {
            return false
        }
        val ips = filter.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
        for (iStr in ips) {
            if (isIP(iStr) && iStr == ip) {
                return true
            } else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) {
                return true
            } else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) {
                return true
            }
        }
        return false
    }
}
